﻿/**
* CRL
*/
using CRL.LambdaQuery;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using System.Linq.Expressions;
using MongoDB.Driver.Linq;
using System.Reflection;
using System.Reflection.Emit;
using CRL.Core;
using CRL;
using CRL.Attribute;
using MongoDB.Bson.Serialization;
using Newtonsoft.Json;
using System.IO;

namespace CRL.Mongo.MongoDBEx
{
    /// <summary>
    /// MongoDB不支持关联和直接语句查询
    /// 部份扩展方法支持
    /// </summary>
    public sealed partial class MongoDBExt
    {
        List<dynamic> GetDynamicResult<TModel>(ILambdaQuery<TModel> iQuery)
        {
            var query = iQuery as MongoDBLambdaQuery<TModel>;
            //var selectField = query.__QueryFields;
            var selectField = query.GetFieldMapping();
  
            var pageIndex = query.SkipPage - 1;
            var pageSize = query.TakeNum;
            var skip = 0;
            long rowNum = 0;
            if (query.TakeNum > 0)
            {
                skip = pageSize * pageIndex;
            }
            if (query.__GroupFields != null)
            {
                var collection = GetCollection<TModel>();
                #region group

                var groupInfo = new BsonDocument();
                var groupField = new BsonDocument();
                foreach (var f in query.__GroupFields)
                {
                    groupField.Add(f.FieldName, "$" + f.FieldName);
                }

                groupInfo.Add("_id", groupField);
                var projection = new BsonDocument();
                foreach (var f in selectField)
                {
                    var method = f.MethodName.ToLower();
                    object sumField = 1;
                    if (!string.IsNullOrEmpty(method))
                    {
                        if (method == "count")
                        {
                            groupInfo.Add(f.ResultName, new BsonDocument("$sum", 1));
                        }
                        else
                        {
                            var memberName = f.QueryField;
                            memberName = System.Text.RegularExpressions.Regex.Replace(memberName, @"\w+\(+(.+?)\)+", "$1");
                            #region 字段间运算
                            string op = "";
                            string opFunc = "";
                            if (memberName.Contains("+"))
                            {
                                op = "+";
                                opFunc = "add";
                            }
                            else if (memberName.Contains("-"))
                            {
                                op = "-";
                                opFunc = "subtract";
                            }
                            else if (memberName.Contains("*"))
                            {
                                op = "*";
                                opFunc = "multiply";
                            }
                            else if (memberName.Contains("1"))
                            {
                                op = "/";
                                opFunc = "divide";
                            }

                            if (!string.IsNullOrEmpty(op))
                            {
                                var arry = memberName.Split(op.First());//仅第一个运算符                                 
                                //等效 "$sum":{$multiply:["$price","$count"]} sum(price*count)
                                var bsArry = new BsonArray();
                                foreach (var field1 in arry)
                                {
                                    bsArry.Add($"${field1}");
                                }
                                var bs1 = new BsonDocument($"${opFunc}", bsArry);
                                groupInfo.Add(f.ResultName, new BsonDocument("$" + method.ToLower(), bs1));
                            }
                            else
                            {
                                groupInfo.Add(f.ResultName, new BsonDocument("$" + method.ToLower(), "$" + memberName));
                            }
                            #endregion
                        }
                        projection.Add(f.ResultName, "$" + f.ResultName);
                    }
                    else
                    {
                        projection.Add(f.ResultName, "$_id." + f.FieldName);
                    }
                }
                foreach (var f in query.__sortFields.Where(b => string.IsNullOrEmpty(b.Item1.ResultName)))
                {
                    //扩展方法排序时，生成默认的projection
                    var f2 = selectField.Find(b => b.QueryField == f.Item1.QueryField);
                    if (f2 == null)
                    {
                        projection.Add(f.Item1.QueryField, "$" + f.Item1.QueryField);
                    }
                }
                var havingExp = query.__MongoHavingCount;
                var filter = query.__MongoDBFilter;
                var aggregate = collection.Aggregate(new AggregateOptions() { AllowDiskUse = true }).Match(filter).Group(groupInfo).Project(projection);

                foreach (var f in query.__sortFields)
                {
                    var resultName = f.Item1.ResultName;
                    if (string.IsNullOrEmpty(resultName))
                    {
                        var f2 = selectField.Find(b => b.QueryField == f.Item1.QueryField);
                        if (f2 != null)//从selectField里查找
                        {
                            resultName = f2.ResultName;
                        }
                        if (string.IsNullOrEmpty(resultName))//按生成的默认值算
                        {
                            resultName = f.Item1.QueryField;
                        }
                    }
                    aggregate = aggregate.Sort(new BsonDocument()
                            .Add(resultName, f.Item2 ? -1 : 1));
                }
                if (havingExp != null)
                {
                    //var filter2 = query.HavingCount(havingExp);
                    var having = new BsonDocument();
                    var op = "";
                    #region op
                    switch (havingExp.ExpType)
                    {
                        case ExpressionType.Equal:
                            op = "eq";
                            break;
                        case ExpressionType.GreaterThan:
                            op = "gt";
                            break;
                        case ExpressionType.GreaterThanOrEqual:
                            op = "gte";
                            break;
                        case ExpressionType.LessThan:
                            op = "lt";
                            break;
                        case ExpressionType.LessThanOrEqual:
                            op = "lte";
                            break;
                        case ExpressionType.NotEqual:
                            op = "ne";
                            break;
                        default:
                            throw new InvalidCastException("不支持的运算符");
                    }
                    #endregion
                    having.AddRange(new BsonDocument()
                        .Add(havingExp.Left.Data.ToString(), new BsonDocument()
                                .Add("$" + op, new BsonInt64(Convert.ToInt64(havingExp.Right.Data)))
                        ));
                    aggregate = aggregate.Match(having);
                }

                if (query.TakeNum > 0)
                {
                    if (skip > 0)
                    {
                        aggregate = aggregate.Skip(skip);
                    }
                    aggregate = aggregate.Limit(pageSize);
                    //rowNum = collection.Count(query.__MongoDBFilter);//todo 总行数
                }
                //var str = aggregate.ToString();
                query.__QueryReturn = () =>
                {
                    return ConvertJsonString(aggregate.ToString());
                };
                var result = aggregate.ToList();
                if (rowNum == 0)
                {
                    rowNum = result.Count();
                }
                var list = new List<dynamic>();
                foreach (var item in result)
                {
                    dynamic obj = new System.Dynamic.ExpandoObject();
                    var dict = obj as IDictionary<string, object>;
                    foreach (var f in selectField)
                    {
                        string columnName = f.FieldName;
                        var resultName = f.ResultName;
                        object value = BsonTypeMapper.MapToDotNetValue(item[columnName]);
                        dict.Add(resultName, value);
                    }
                    list.Add(obj);
                }
                return list;
                #endregion
            }
            else if (query.__Relations != null)
            {
                return getJoinResult(query);
            }
            else if (query.__DistinctFields)
            {
                var collection = GetCollection<TModel>();
                #region distinct
                string fieldName = selectField.FirstOrDefault().ResultName;
                FieldDefinition<TModel, dynamic> distinctField = fieldName;
                var query2 = collection.Distinct(distinctField, query.__MongoDBFilter);
                query.__QueryReturn = () =>
                {
                    return ConvertJsonString(query2.ToString());
                };
                return query2.ToList();
                #endregion
            }
            else
            {
                var collection = GetCollection<TModel>();
                #region 动态类型
                var query2 = collection.Find(query.__MongoDBFilter);
                if (query.TakeNum > 0)
                {
                    query2.Limit(pageSize);
                    if (skip > 0)
                    {
                        query2.Skip(skip);
                    }
                }
                //todo 并未生成Project
                var projection = new BsonDocument();
                foreach (var f in selectField)
                {
                    projection.Add(f.FieldName, 1);
                }
                var queryBson = query2.Project(projection);
                query.__QueryReturn = () =>
                {
                    return ConvertJsonString(queryBson.ToString());
                };
                var result = queryBson.ToList();
                var list = new List<dynamic>();
                var fields = TypeCache.GetTable(typeof(TModel)).FieldsDic;
                foreach (var item in result)
                {
                    dynamic obj = new System.Dynamic.ExpandoObject();
                    var dict = obj as IDictionary<string, object>;
                    foreach (var f in selectField)
                    {
                        string columnName = f.FieldName;
                        var resultName = f.ResultName;
                        object value = BsonTypeMapper.MapToDotNetValue(item[columnName]);
                        dict.Add(resultName, value);
                    }
                    list.Add(obj);
                }
                #endregion
                return list;
            }
        }
        #region QueryDynamic
        public override List<dynamic> QueryDynamic(LambdaQueryBase query)
        {
            throw new NotSupportedException("MongoDB暂未实现此方法");
        }
        public List<dynamic> QueryDynamic2<TModel>(LambdaQuery<TModel> query) where TModel : class
        {
            var result = GetDynamicResult(query);
            return result;
        }
        #endregion

        #region QueryResult

        /// <summary>
        /// 按select返回指定类型
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="query"></param>
        /// <returns></returns>
        List<TResult> QueryResult<TResult>(LambdaQueryBase query)
        {
            var typeDb = this.GetType();
            var method = typeDb.GetMethod(nameof(QueryResultByType), BindingFlags.NonPublic | BindingFlags.Instance);
            var result = method.MakeGenericMethod(new Type[] { query.__MainType, typeof(TResult) }).Invoke(this, new object[] { query });
            return result as List<TResult>;

        }

        public override List<TResult> QueryResult<TResult>(LambdaQueryBase query, NewExpression newExpression = null)
        {
            if (newExpression == null)
            {
                return QueryResult<TResult>(query);
            }
            var typeDb = this.GetType();
            var method = typeDb.GetMethod(nameof(QueryResultNewExpression), BindingFlags.NonPublic | BindingFlags.Instance);
            var result = method.MakeGenericMethod(new Type[] { query.__MainType, typeof(TResult) }).Invoke(this, new object[] { query, newExpression });
            return result as List<TResult>;
        }

        List<TResult> QueryResultByType<TModel, TResult>(LambdaQuery<TModel> query) where TModel : class
        {
            var result = GetDynamicResult(query);
            var type = typeof(TResult);
            var pro = type.GetProperties();
            var list = new List<TResult>();
            var reflection = ReflectionHelper.GetInfo<TResult>();
            foreach (var item in result)
            {
                var dict = item as IDictionary<string, object>;
                var obj = (TResult)System.Activator.CreateInstance(type);
                foreach (var f in pro)
                {
                    string columnName = f.Name;
                    if (dict.ContainsKey(columnName))
                    {
                        object value = dict[columnName];
                        var access = reflection.GetAccessor(columnName);
                        access.Set((TResult)obj, value);
                    }
                }
                list.Add(obj);
            }
            return list;
        }


        static Func<ObjContainer, T> CreateObjectGenerator<T>(ConstructorInfo constructor)
        {
            var type = typeof(ObjContainer);
            var parame = Expression.Parameter(type, "par");
            var parameters = constructor.GetParameters();
            List<Expression> arguments = new List<Expression>(parameters.Length);
            foreach (var parameter in parameters)
            {
                var method = ObjContainer.GetMethod(parameter.ParameterType, true);
                var getValue = Expression.Call(parame, method, Expression.Constant(parameter.Name));
                arguments.Add(getValue);
            }
            var body = Expression.New(constructor, arguments);
            var ret = Expression.Lambda<Func<ObjContainer, T>>(body, parame).Compile();
            return ret;
        }


        static Func<ObjContainer, T> CreateObjectGeneratorFromMapping<T>(IEnumerable<FieldMapping> mapping)
        {
            var objectType = typeof(T);
            var fields = TypeCache.GetProperties(objectType, true);
            var parame = Expression.Parameter(typeof(ObjContainer), "par");
            var memberBindings = new List<MemberBinding>();
            //按顺序生成Binding
            //int i = 0;
            foreach (var mp in mapping)
            {
                if (!fields.ContainsKey(mp.ResultName))
                {
                    continue;
                }
                var m = fields[mp.ResultName].GetPropertyInfo();
                var method = ObjContainer.GetMethod(m.PropertyType, true);
                //Expression getValue = Expression.Call(method, parame);
                var getValue = parame.Call(method.Name, Expression.Constant(mp.ResultName));
                if (m.PropertyType.IsEnum)
                {
                    getValue = Expression.Convert(getValue, m.PropertyType);
                }
                var bind = (MemberBinding)Expression.Bind(m, getValue);
                memberBindings.Add(bind);
                //i += 1;
            }
            Expression expr = Expression.MemberInit(Expression.New(objectType), memberBindings);
            var ret = Expression.Lambda<Func<ObjContainer, T>>(expr, parame);
            return ret.Compile();
        }


        List<TResult> QueryResultNewExpression<TModel, TResult>(LambdaQuery<TModel> query, NewExpression newExpression) where TModel : class
        {
            //query.Select(newExpression);
            var result = GetDynamicResult(query);
            var list = new List<TResult>();
            Func<ObjContainer, TResult> objCreater;
            var parameters = newExpression.Constructor.GetParameters();
            //当匿名类型指定了类型,没有构造参数
            if (parameters.Length > 0)
            {
                objCreater = CreateObjectGenerator<TResult>(newExpression.Constructor);
            }
            else
            {
                objCreater = CreateObjectGeneratorFromMapping<TResult>(query.GetFieldMapping());
            }

            foreach (IDictionary<string, object> item in result)
            {
                var objC = new ObjContainer(item);
                var obj = objCreater(objC);
                list.Add(obj);
            }
            return list;
        }
        #endregion

        public override List<TModel> QueryOrFromCache<TModel>(ILambdaQuery<TModel> iQuery, out string cacheKey)
        {
            cacheKey = "none";
            var query = iQuery as MongoDBLambdaQuery<TModel>;
            var collection = GetCollection<TModel>();
            long rowNum = 0;
            var query2 = collection.Find(query.__MongoDBFilter);
            foreach (var f in query.__sortFields)
            {
                query2 = query2.Sort(new BsonDocument()
                            .Add(f.Item1.QueryField, f.Item2 ? -1 : 1));
            }
 
            if (query.TakeNum > 0)
            {
                var pageIndex = query.SkipPage - 1;
                var pageSize = query.TakeNum;
                var skip = pageSize * pageIndex;
                if (skip > 0)
                {
                    query2.Skip(skip);
                }
                query2.Limit(pageSize);
                rowNum = collection.Find(query.__MongoDBFilter).CountDocuments();
            }
            var result = query2.ToList();
            if (rowNum == 0)
            {
                rowNum = result.Count();
            }
            query.__RowCount = (int)rowNum;
            //SetOriginClone(result);
            return result;
        }
        public override Dictionary<TKey, TValue> ToDictionary<TModel, TKey, TValue>(ILambdaQuery<TModel> query)
        {
            var dic = new Dictionary<TKey, TValue>();
            var result = GetDynamicResult(query);
            if (result.Count == 0)
            {
                return dic;
            }
            var first = result.First() as IDictionary<string, object>;
            var keys = first.Keys.ToList();
            var keyName = keys[0];
            var valueName = keys[1];
            foreach (var item in result)
            {
                var obj = item as IDictionary<string, object>;
                dic.Add((TKey)obj[keyName], (TValue)obj[valueName]);
            }
            return dic;
        }
        public override dynamic QueryScalar<TModel>(ILambdaQuery<TModel> query)
        {
            var result = GetDynamicResult(query);
            if (result.Count == 0)
            {
                return null;
            }
            var first = result.First() as IDictionary<string, object>;
            var keys = first.Keys.ToList();
            return first[keys.First()];
        }
        internal static BsonDocument RenderToBsonDocument<T>(FilterDefinition<T> filter)
        {
            var serializerRegistry = BsonSerializer.SerializerRegistry;
            var documentSerializer = serializerRegistry.GetSerializer<T>();
            return filter.Render(documentSerializer, serializerRegistry);
        }
        List<dynamic> getJoinResult<T>(MongoDBLambdaQuery<T> query)
        {
            var pageIndex = query.SkipPage - 1;
            var pageSize = query.TakeNum;
            var skip = 0;
            if (query.TakeNum > 0)
            {
                skip = pageSize * pageIndex;
            }
            // 匹配条件
            //var match = PipelineStageDefinitionBuilder.Match<BsonDocument>(RenderToBsonDocument(query.__MongoDBFilter));
            
            // 组合并形成最终查询
            var stages = new List<IPipelineStageDefinition> {  };
            Dictionary<Type, string> asNames = new Dictionary<Type, string>();
            foreach (var relation in query.__Relations)
            {
                var typeJoin = relation.Key.OriginType;
                var arry = relation.Value.condition.ToLower().Split(new string[] { "and" }, StringSplitOptions.RemoveEmptyEntries);
                if (arry.Length > 1)
                {
                    throw new Exception($"不支持多个条件 {relation.Value.condition}");
                }

                var arry2 = arry[0].Split('=');
                var field1 = arry2[0];
                var field2 = arry2[1];
                var findInMain = TypeCache.GetTable(typeof(T)).FieldsDic.ContainsKey(field1);
                string foreignField;
                string localField;
                if (findInMain)
                {
                    localField = field1;
                    foreignField = field2;
                }
                else
                {
                    localField = field2;
                    foreignField = field1;
                }
                var asName = "__" + getTableName(typeJoin);
                // 关联条件
                var lookup = PipelineStageDefinitionBuilder.Lookup<BsonDocument, BsonDocument, BsonDocument>(
                        _MongoDB.GetCollection<BsonDocument>(getTableName(typeJoin)), localField, foreignField, asName);
                asNames.Add(typeJoin, asName);
                stages.Add(lookup);
            }
            if (query.TakeNum > 0)
            {
                if (skip > 0)
                {
                    stages.Add(PipelineStageDefinitionBuilder.Skip<BsonDocument>(skip));
                }
                stages.Add(PipelineStageDefinitionBuilder.Limit<BsonDocument>(pageSize));
            }
            var pipeline = PipelineDefinition<BsonDocument, BsonDocument>.Create(stages);
            var dataFacet = AggregateFacet.Create("dataFacet", pipeline);

            var countFacet = AggregateFacet.Create("countFacet",
            PipelineDefinition<BsonDocument, AggregateCountResult>.Create(new[]
            {
                PipelineStageDefinitionBuilder.Count<BsonDocument>()
            }));
            var aggregate = _MongoDB.GetCollection<BsonDocument>(getTableName(typeof(T))).Aggregate()
            .Match(RenderToBsonDocument(query.__MongoDBFilter))
            .Facet(dataFacet, countFacet);
            query.__QueryReturn = () =>
            {
                return ConvertJsonString(aggregate.ToString());
            };
            var dataList = aggregate.ToList();
            //todo 统计了所有没关联上的数据
            var count = dataList.First().Facets[1].Output<AggregateCountResult>().FirstOrDefault()?.Count;
            query.__RowCount = count.HasValue ? (int)count.Value : 0;
            var selectField = query.GetFieldMapping();
            var list = new List<dynamic>();
            foreach (var item in dataList.First().Facets[0].Output<BsonDocument>())
            {
                dynamic obj = new System.Dynamic.ExpandoObject();
                var dict = obj as IDictionary<string, object>;
                var asObjs = asNames.ToDictionary(b => b.Key, b => item.GetValue(b.Value).AsBsonArray[0].AsBsonDocument);
                foreach (var f in selectField)
                {
                    string columnName = f.FieldName;
                    var bsValue = asNames.ContainsKey(f.ModelType) ? asObjs[f.ModelType].GetValue(columnName) : item.GetValue(columnName);
                    var value = BsonTypeMapper.MapToDotNetValue(bsValue);
                    var resultFieldName = f.ResultName;
                    if (!dict.ContainsKey(resultFieldName))
                    {
                        dict.Add(resultFieldName, value);
                    }
                }
                list.Add(obj);
            }
            return list;
        }

        static string ConvertJsonString(string str)
        {
            return str;
            //格式化json字符串
            JsonSerializer serializer = new JsonSerializer();
            TextReader tr = new StringReader(str);
            JsonTextReader jtr = new JsonTextReader(tr);
            object obj = serializer.Deserialize(jtr);
            if (obj != null)
            {
                StringWriter textWriter = new StringWriter();
                JsonTextWriter jsonWriter = new JsonTextWriter(textWriter)
                {
                    Formatting = Formatting.Indented,
                    Indentation = 4,
                    IndentChar = ' '
                };
                serializer.Serialize(jsonWriter, obj);
                return textWriter.ToString();
            }
            else
            {
                return str;
            }
        }
    }
}
